#include "message_details.h"
#include <queue>
#include <type_traits>
#include <vector>
#include <string_view>
#include <cstring> // memcpy
#include <string>
#include <filesystem>
#include <sstream>
#include <variant> // ::get<xxx>(variant)

using namespace ::std::string_view_literals;

namespace wilson::http::details
{
    ::std::span<const ::std::byte> message_from_net::
    to_payload() const
    {
        return m_storage->read_block(0);
    }

    ::std::string_view message_from_net::
    to_payload_string() const
    {
        auto payload = to_payload();
        return { 
            reinterpret_cast<const char*>(payload.data()), 
        };
    }

    void message_to_net::fill_in_storage() const
    {
        ::std::vector<::std::string_view> message_items;
        
        // response line
        message_items.emplace_back(to_string(m_version));
        message_items.emplace_back(" ");
        auto status_str_code = ::std::to_string(
            static_cast<::std::underlying_type_t<status>>(m_status)
        );
        message_items.emplace_back(status_str_code);
        message_items.emplace_back(" ");
        message_items.emplace_back(to_string(m_status));
        message_items.emplace_back(CRLF);

        // headers
        for (const auto& [k, v] : m_headers)
        {
            message_items.emplace_back(k);
            message_items.emplace_back(": ");
            message_items.emplace_back(v);  
            message_items.emplace_back(CRLF);
        }
        message_items.emplace_back(CRLF);
        
        // body
        if (m_body.index() == 0) // normal body bytes
        {
            message_items.emplace_back(::std::get<0>(m_body));
            message_items.emplace_back(CRLF CRLF);
        }

        /* Calculate the actual memory size required, 
         * allocate so much memory, and finally 
         * copy the data to it.
         */
        size_t space_usage{1};
        for (const auto& item : message_items)
        {
            space_usage += item.size();
        }

        auto buffer = m_storage->next_write_block(space_usage);
        auto* cur = buffer.data();
        for (const auto& item : message_items)
        {
            ::memcpy(cur, item.data(), item.size());
            cur += item.size();
        }

        // terminator
        *cur = ::std::byte{};
        m_filled = true;
    }
    
    ::std::pair<bool, ::std::filesystem::path> message_to_net::attached_file()
    {
        if (m_body.index() == 0)
            return ::std::make_pair(false, ::std::filesystem::path{});
        ::std::string filename = ::std::get<1>(m_body);
        return ::std::make_pair(true, filename);
    }
    
    ::std::span<const ::std::byte> 
    message_to_net::
    to_payload() const
    {
        ::std::vector<::std::span<const ::std::byte>> result;
        if (!m_filled)
            fill_in_storage();
        auto temp = m_storage->read_block(0);

        /* The reason for subtracting 1 here is that `temp` 
         * allocates an extra byte to store the terminator. 
         * The `size` required by `span` which is the return value
         * is the number of characters, **NOT** the number 
         * of bytes actually occupied by the payload.
         */
        return { temp.data(), temp.size() - 1 };
    }

    ::std::string_view message_to_net::
    to_payload_string() const
    {
        auto payload = to_payload();
        return { 
            reinterpret_cast<const char*>(payload.data()), 

            /* Because the storage of `message_to_net` object 
             * was allocated with exactly size which evaluated 
             * at the end of `fill_in_storage` function and 
             * absolutely fit the message payload, 
             * no any byte(s) were wasted.
             */
            payload.size()
        };
    }
}
